
#include <QtGlobal>
#include <QPointer>
#include <QApplication>
#include <QCoreApplication>
#include <QTranslator>
#include <QLibraryInfo>
#include <QTextCodec>
#include <QJsonDocument>
#include <QTimer>
#include <QRegExp>
#include <QRegExpValidator>
#include <string>

#if defined(Q_OS_LINUX) || defined(Q_CC_MINGW)
#include <signal.h>
#endif

#if defined(Q_OS_WIN) || defined(Q_CC_MINGW)
#include <winsock2.h>
#include <windows.h>
#include <iphlpapi.h>
#include <shlobj.h>
#include <shellapi.h>

#define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004
#define DISABLE_NEWLINE_AUTO_RETURN 0x0008

#define REG_VALUE_SIZE  512

// Memory funcs
#define MALLOC(x) HeapAlloc(GetProcessHeap(), 0, (x))
#define FREE(x) HeapFree(GetProcessHeap(), 0, (x))
#define SIZE(x) HeapSize(GetProcessHeap(), 0, (x))
#endif

#include "utils.h"

// JsonUtils
namespace JsonUtils {
// Dumper
void Dumper(const QJsonObject &json_object, const QString &header, const QString &prefix)
{
    QJsonDocument doc(json_object);
    QString jsonstr = QString::fromLocal8Bit(doc.toJson(QJsonDocument::Indented));
    QStringList lines = jsonstr.split(QLatin1String("\n"));
    if (!header.isEmpty()) {
        qDebug().noquote() << QString(QLatin1String("%1:")).arg(header);
    }
    for (const QString &line : qAsConst(lines)) {
        if (!line.trimmed().isEmpty()) {
            if (prefix.isEmpty()) {
                qDebug().noquote() << QString(QLatin1String("%1")).arg(line);
            } else {
                qDebug().noquote() << QString(QLatin1String("%1: %2")).arg(prefix, line);
            }
        }
    }
}
void Dumper(const QJsonArray &json_array, const QString &header, const QString &prefix)
{
    QJsonDocument doc(json_array);
    QString jsonstr = QString::fromLocal8Bit(doc.toJson(QJsonDocument::Indented));
    QStringList lines = jsonstr.split(QLatin1String("\n"));
    if (!header.isEmpty()) {
        qDebug().noquote() << QString(QLatin1String("%1:")).arg(header);
    }
    for (const QString &line : qAsConst(lines)) {
        if (!line.trimmed().isEmpty()) {
            qDebug().noquote() << QString(QLatin1String("%1: %2")).arg(prefix, line);
        }
    }
}
} // JsonUtils
// StringUtils
namespace StringUtils {
QTextCodec *GetTextCodec(const QString &codec_name)
{
    QString codec_name_a = codec_name;
    const QList<QByteArray> &available_codecs = QTextCodec::availableCodecs();
    if (!available_codecs.contains(codec_name.toLocal8Bit())) {
        codec_name_a = QString::fromLocal8Bit(QTextCodec::codecForLocale()->name());
        qWarning().noquote() << QString(QLatin1String("Codec {%1} is invalid, use {%2} to replaced.")).arg(codec_name, codec_name_a);
    }
    QTextCodec *codec = QTextCodec::codecForName(codec_name_a.toLocal8Bit().constData());

    return codec;
}
QString Convert(const uchar *u_str, int length)
{
    QString out;
    QChar *buffer = new QChar[length];

    for (int i=0; i<length; i++) {
        buffer[i] = QChar(QLatin1Char(u_str[i]));
    }
    // TODO: 需要测试此处是否正常触发深度拷贝，以免数据丢失
    //out.setUnicode(buffer, length);
    out.setRawData(buffer, length);
    delete [] buffer;
    return out;
}
QString Convert(const QByteArray &src)
{
    QTextCodec::ConverterState state;
    QTextCodec *codec = QTextCodec::codecForName("UTF-8");

    QString out = codec->toUnicode(src.constData(), src.size(), &state);
    if (state.invalidChars > 0) {
        // qDebug().noquote() << __FUNCTION__ << "Invalid Chars:" << state.invalidChars << "Remaind Chars:" << state.remainingChars;

    switch (state.invalidChars) {
    case 3: out = QTextCodec::codecForName( "Unicode" )->toUnicode(src); break;
    case 4: out = QTextCodec::codecForName( "GBK" )->toUnicode(src); break;
    case 6: out = QTextCodec::codecForName( "GBK" )->toUnicode(src); break;
        default: out = QTextCodec::codecForName( "System" )->toUnicode(src); break;
        }
    }
    return out;
}
QByteArray ConvertFrom(const QString &src, const QString &codec_name)
{
    QTextCodec *codec = GetTextCodec(codec_name);
    QByteArray out;
    if (codec) {
        out = codec->fromUnicode(src);
    }
    return out;
}
QByteArray ConvertTo(const QString &src, const QString &codec_name)
{
    QTextCodec *codec = GetTextCodec(codec_name);
    QByteArray out;
    if (codec) {
        out = codec->toUnicode(src.toLatin1()).toLatin1();
    }
    return out;
}
QString toHexString(const QString &src, const QString &codec_name)
{
    QByteArray buffer = ConvertFrom(src, codec_name);

    return QString::fromLocal8Bit(buffer.toHex().toUpper());
}
QString fromHexString(const QString &src, const QString &codec_name)
{
    QByteArray buffer = ConvertTo(src, codec_name);

    return QString::fromLocal8Bit(QByteArray::fromHex(buffer));
}
wchar_t *toWCharArray(const char *str, int str_len)
{
    size_t len_str = 0;
    size_t len_wstr = 0;
    size_t len_convert = 0;
    wchar_t *out = nullptr;

    if (str) {
        if (str_len < 0) {
            len_str = strlen(str) + sizeof(char);
        } else {
            len_str = str_len;
        }
        len_wstr = mbstowcs(nullptr, str, len_str) + sizeof(wchar_t);

        out = new wchar_t[len_wstr];
        wmemset(out, 0, len_wstr);

        len_convert = mbstowcs(out, str, len_str);
        if (len_convert == (size_t)-1) {
            qCritical().noquote() << __FUNCTION__ << "Convert to wchar_t[] failed!";
            delete [] out;
            out = nullptr;
        }
    }

    return out;
}
char* toCharArray(const wchar_t *wstr, int wstr_len)
{
    size_t len_str = 0;
    size_t len_wstr = 0;
    size_t len_convert = 0;
    char *out = nullptr;

    if (wstr) {
        if (wstr_len < 0) {
            len_wstr = wcslen(wstr) + sizeof(wchar_t);
        } else {
            len_wstr = wstr_len;
        }
        len_str = wcstombs(nullptr, wstr, len_wstr) + sizeof(char);

        out = new char[len_str];
        memset(out, 0, len_str);

        len_convert = wcstombs(out, wstr, len_wstr);
        if (len_convert == (size_t)-1) {
            qCritical().noquote() << __FUNCTION__ << "Convert to char[] failed!";
            delete [] out;
            out = nullptr;
        }
    }

    return out;
}
size_t toWString(const std::string &src, std::wstring &dst)
{
    size_t len_str = 0;
    size_t len_wstr = 0;
    size_t len_convert = 0;
    wchar_t *out = nullptr;

    if (!src.empty()) {
        len_str = strlen(src.c_str()) + sizeof(char);
        len_wstr = mbstowcs(nullptr, src.c_str(), len_str) + sizeof(wchar_t);

        out = new wchar_t[len_wstr];
        wmemset(out, 0, len_wstr);

        len_convert = mbstowcs(out, src.c_str(), len_str);
        if (len_convert == (size_t)-1) {
            qCritical().noquote() << __FUNCTION__ << "Convert to std::wstring failed!";
        } else {
            dst = std::wstring(out);
        }

        delete [] out;
        out = nullptr;
    }

    return len_convert;
}
int toString(const std::wstring &src, std::string &dst)
{
    size_t len_str = 0;
    size_t len_wstr = 0;
    size_t len_convert = 0;
    char *out = nullptr;

    if (!src.empty()) {
        len_str = wcslen(src.c_str()) + sizeof(char);
        len_wstr = wcstombs(nullptr, src.c_str(), len_str) + sizeof(wchar_t);

        out = new char[len_wstr];
        memset(out, 0, len_wstr);

        len_convert = wcstombs(out, src.c_str(), len_str);
        if (len_convert == (size_t)-1) {
            qCritical().noquote() << __FUNCTION__ << "Convert to std::string failed!";
        } else {
            dst = std::string(out);
        }

        delete [] out;
        out = nullptr;
    }

    return len_convert;
}
} // StringUtils

// ListUtils
namespace ListUtils {
// QList<QString> --> QList<int>
QList<int> Convert(const QList<QString> &src)
{
    QList<int> out;
    if (!src.isEmpty()) {
        for (const QString &s : src) {
            if (!s.isEmpty()) {
                out << s.toInt();
            }
        }
    }
    return out;
}
// QList<int> --> QList<QString>
QList<QString> Convert(const QList<int> &src)
{
    QList<QString> out;
    if (!src.isEmpty()) {
        for (int s : src) {
            out << QString::number(s);
        }
    }
    return out;
}

// QList<QString> --> QList<QVariant>
QList<QVariant> toVariants(const QList<QString> &src)
{
    QVariantList out;
    if (!src.isEmpty()) {
        for (const QString &s : src) {
            if (!s.isEmpty()) {
                out << QVariant::fromValue(s);
            }
        }
    }
    return out;
}
// QList<int> --> QList<QVariant>
QList<QVariant> toVariants(const QList<int> &src)
{
    QVariantList out;
    if (!src.isEmpty()) {
        for (int s : src) {
            out << QVariant::fromValue(s);
        }
    }
    return out;
}
} // ListUtils

// MiscUtils
namespace MiscUtils {
// Global Variables
QTimer *g_exit_timer = nullptr;
QList<QSharedPointer<QTranslator>> g_translators;
// HightDPI
void EnableHightDPI()
{
#if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0))
    QApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
    QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
#endif
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
    QApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);
#endif
}
// Sleep
void m_sleep(quint32 ms)
{
    if (ms > 0) {
#if defined (Q_OS_WIN)
        Sleep(ms);
#else
        usleep(ms * 1000);
#endif
    }
}
// Signal Methods
void ExitAfter(qint32 msec)
{
    g_exit_timer = new QTimer(qApp);
    QObject::connect(g_exit_timer, &QTimer::timeout, qApp, &QCoreApplication::quit);
    g_exit_timer->start(msec);
}
void HandelSignal(qint32 signal_number, SignalHandlerCallback callback)
{
    Q_UNUSED(signal_number)
    Q_UNUSED(callback)

#if defined(Q_OS_LINUX) || defined(Q_CC_MINGW)
    signal(signal_number, callback);
#endif
}
// Url Validator
bool isValidUrl(QString url)
{
    int pos = 0;

    QRegExp regexp;
    //url regular expression
    regexp.setPattern(QLatin1String("(https?|ftp|file)://[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]"));
    regexp.setCaseSensitivity(Qt::CaseInsensitive);

    QRegExpValidator validator;
    validator.setRegExp(regexp);

    return (validator.validate(url, pos) == QValidator::Acceptable);
}

// Translator
#define APPEND_TO_LIST(LIST, ITEM) { QString item(ITEM); if (!item.isEmpty() && !LIST.contains(item)) { LIST.append(item); } }
static void CleanupTranslator()
{
    while (!g_translators.isEmpty()) {
        QSharedPointer<QTranslator> translator = g_translators.takeFirst();
        qApp->removeTranslator(translator.data());
        if (!translator.isNull()) {
            translator->deleteLater();
        }
    }
}
bool LoadLanguage(const QString &filename, const QString &filepath, const QString &language)
{
    qAddPostRoutine(CleanupTranslator);
    if (language.startsWith(QLatin1String("C"))) {
        return true;
    }
    if (language.startsWith(QLatin1String("en_US"))) {
        return true;
    }

    QLocale locale(language);
    QStringList translate_files;
    QStringList translation_pathes;

    APPEND_TO_LIST(translate_files, QLatin1String("qt"))
    APPEND_TO_LIST(translate_files, QLatin1String("qtbase"))
    APPEND_TO_LIST(translate_files, filename)

    APPEND_TO_LIST(translation_pathes, filepath)
    APPEND_TO_LIST(translation_pathes, QLatin1String(":/i18n"))
    APPEND_TO_LIST(translation_pathes, QString(QLatin1String("%1/translations")).arg(QCoreApplication::applicationDirPath()))
    APPEND_TO_LIST(translation_pathes, QLibraryInfo::location(QLibraryInfo::TranslationsPath))

#ifdef DEBUG_TRANSLATOR
    FUNCTION_TRACE << "TranslateFile:" << translate_files.join(QLatin1String(", "));
    FUNCTION_TRACE << "TranslateFile Search Path:" << translation_pathes.join(QLatin1String(", "));
#endif

    bool loaded = false;
    bool found = false;
    for (const QString &translate_file : qAsConst(translate_files)) {
        found = false;
        for (const QString &translations_path : qAsConst(translation_pathes)) {
            loaded = false;
            QSharedPointer<QTranslator> translator = QSharedPointer<QTranslator>(new QTranslator());
            if (!loaded) {
                loaded = translator->load(locale, translate_file, QLatin1String("_"), translations_path, QLatin1String(".qm"));
            }
            if (!loaded) {
                loaded = translator->load(locale, translate_file, QLatin1String("."), translations_path, QLatin1String(".qm"));
            }
            if (!loaded) {
                loaded = translator->load(translate_file, translations_path, QLatin1String(".qm"));
            }
            if (loaded) {
                found = true;
                if (translator->isEmpty()) {
#if QT_VERSION > QT_VERSION_CHECK(5, 15, 0)
                    qWarning().noquote() << "Translator:" << translator->language() << "is empty!";
#else
                    qWarning().noquote() << "Translator:" << language << "is empty!";
#endif
                    translator->deleteLater();
                    continue;
                } else {
                    if (qApp->installTranslator(translator.data())) {
#ifdef DEBUG_TRANSLATOR
                        FUNCTION_TRACE << "Translator:" << QString(QLatin1String("Load language %1 from %2")).arg(language, translate_file);
#endif
                        g_translators.append(translator);
                    } else {
#if QT_VERSION > QT_VERSION_CHECK(5, 15, 0)
                        qWarning().noquote() << QString(QLatin1String("Load language %1 from %2 failed!")).arg(language, translator->filePath());
#else
                        qWarning().noquote() << QString(QLatin1String("Load language %1 from %2 failed!")).arg(language, translate_file);
#endif
                        if (!translator.isNull()) {
                            translator->deleteLater();
                        }
                    }
                    break;
                }
            }
        }
        if (!found) {
            qWarning().noquote() << QString(QLatin1String("Cannot find translation file %1 for language %2, skip!")).arg(translate_file, language);
        }
    }
    return loaded;
}
// Symbol Link Reader
QString RealPath(const QString &file, int max_depth)
{
    int i = 0;
    QString realpath;
    QFileInfo fileinfo(file);
    while (i < max_depth && fileinfo.isSymLink()) {
        realpath = fileinfo.symLinkTarget();
        fileinfo.setFile(realpath);
        i++;
    }
    return realpath;
}
} // MiscUtils

// WinUtils
namespace WinUtils {
#if defined(Q_OS_WIN) || defined(Q_CC_MINGW)
// Global Variables
static HANDLE g_htoken;
static TOKEN_PRIVILEGES g_token_privileges;
// Register
QVariant QueryRegister(HKEY hRoot, const QString &Key, const QString &ValueName, DWORD *type)
{
    QVariant value;
    DWORD retval = ERROR_SUCCESS;
    LPVOID valuedata = nullptr;
    HKEY hKey = nullptr;
    DWORD size = 0;

    retval = RegOpenKeyEx(hRoot, Key.toStdWString().c_str(), 0, KEY_READ, &hKey);
    if (retval != ERROR_SUCCESS) {
        if (retval == ERROR_ACCESS_DENIED) {
            qCritical().noquote() << "Please use Administrator Privilege !";
        } else if (retval == ERROR_FILE_NOT_FOUND) {
            qCritical().noquote() << QString("RegOpenKeyEx [%1]: Cannot find Key [%2]").arg(QString::number(retval), Key);
        } else {
            qCritical().noquote() << QString("RegOpenKeyEx [%1]: Open Key [%2] failed!").arg(QString::number(retval), Key);
        }
        RegCloseKey(hKey);
        RegCloseKey(hRoot);
        return value;
    }

    RegQueryValueEx(hKey, ValueName.toStdWString().c_str(), 0, type, NULL, &size);
    valuedata = (LPVOID) MALLOC (size);

    retval = RegQueryValueEx(hKey, ValueName.toStdWString().c_str(), 0, type, (LPBYTE)valuedata, &size);
    if (retval != ERROR_SUCCESS) {
        qCritical().noquote() << QString("RegQueryValueEx [%1]: Query Value [%3] of Key [%2] failed!").arg(QString::number(retval), Key, ValueName);
        RegCloseKey(hKey);
        RegCloseKey(hRoot);
        FREE(valuedata);
        valuedata = nullptr;
        return value;
    }

    // for(DWORD i=0; i<size; i++) {
    //     printf("%02x,", ((LPBYTE)valuedata)[i]);
    // }
    // printf("\n");

    switch (*type) {
    case REG_BINARY:
        value = QVariant::fromValue(QByteArray::fromRawData((char*)valuedata, size));
        break;
    default:
        break;
    }

    RegCloseKey(hKey);
    RegCloseKey(hRoot);
    FREE(valuedata);
    valuedata = nullptr;

    // FUNCTION_TRACE << "Value:" << value.toByteArray().toHex(',');

    return value;
}
// Console
bool AttachOutputToConsole(void)
{
    HANDLE ConsoleHandleOut, ConsoleHandleError;
    DWORD OriginalOutMode = 0, OriginalErrorMode = 0;
    DWORD RequestedModes = ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN;

    if (AttachConsole(ATTACH_PARENT_PROCESS)) {
        // Redirect unbuffered STDOUT to the console
        ConsoleHandleOut = GetStdHandle(STD_OUTPUT_HANDLE);
        if (ConsoleHandleOut != INVALID_HANDLE_VALUE) {
            freopen("CONOUT$", "w", stdout);
            setvbuf(stdout, nullptr, _IONBF, 0);
            GetConsoleMode(ConsoleHandleOut, &OriginalOutMode);
            SetConsoleMode(ConsoleHandleOut, OriginalOutMode | RequestedModes);
        } else {
            return false;
        }
        // Redirect unbuffered STDERR to the console
        ConsoleHandleError = GetStdHandle(STD_ERROR_HANDLE);
        if (ConsoleHandleError != INVALID_HANDLE_VALUE) {
            freopen("CONOUT$", "w", stderr);
            setvbuf(stderr, nullptr, _IONBF, 0);
            GetConsoleMode(ConsoleHandleError, &OriginalErrorMode);
            SetConsoleMode(ConsoleHandleError, OriginalErrorMode | RequestedModes);
        } else {
            return false;
        }
        return true;
    }
    // Not a console application
    return false;
}
// DesktopUI
void DesktopAndTaskBarSetVisible(bool visible)
{
    HWND desktop = FindWindow(L"ProgMan", NULL);
    HWND taskbar = FindWindow(L"Shell_TrayWnd", NULL);
    if (visible) {
        ShowWindow(desktop, SW_SHOW);
        ShowWindow(taskbar, SW_SHOW);
    } else {
        ShowWindow(desktop, SW_HIDE);
        ShowWindow(taskbar, SW_HIDE);
    }
}
// Privileges
bool RunAsAdmin()
{
    if (IsUserAnAdmin()) {
        return false;
    }
    QStringList args = QCoreApplication::arguments();
    if (args.count() < 2 || args.at(1) != "runas") {
        QString appexe = QCoreApplication::applicationFilePath();
        HINSTANCE hInstance = ShellExecuteA(nullptr, "runas", appexe.toStdString().c_str(), "runas", nullptr, SW_SHOWNORMAL);
        if (hInstance > (HINSTANCE)32) {
            return true;
        }
    }
    return false;
}

// TODO: Need More Check
// Url: https://learn.microsoft.com/zh-cn/windows/win32/api/processthreadsapi/nf-processthreadsapi-openprocesstoken
// Url: https://learn.microsoft.com/zh-cn/windows/win32/secauthz/enabling-and-disabling-privileges-in-c--
bool RequestPrivileges()
{
    bool retval = false;
    retval = OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY, &g_htoken);
    if (retval && g_htoken) {
        LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &g_token_privileges.Privileges[0].Luid);
        g_token_privileges.PrivilegeCount = 1;
        g_token_privileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
        retval = AdjustTokenPrivileges(g_htoken, FALSE, &g_token_privileges, 0, (PTOKEN_PRIVILEGES)NULL, 0);
    }
    return retval;
}

bool ReleasePrivileges()
{
    bool retval = false;
    if (retval && g_htoken) {
        g_token_privileges.Privileges[0].Attributes = 0;
        retval = AdjustTokenPrivileges(g_htoken, FALSE, &g_token_privileges, 0, (PTOKEN_PRIVILEGES)NULL, 0);
    }
    return retval;
}
#endif
} // WinUtils

